
import 'dart:convert';
import 'dart:io';

import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter/return_code.dart';
import 'package:fimber/fimber.dart';
import 'package:flutter_hls_parser/flutter_hls_parser.dart';
import 'package:path_provider/path_provider.dart';
import 'package:xim/common/util/datetime.dart';
import 'package:path/path.dart' as path;
import 'package:xim/common/util/file.dart';
import 'package:xim/common/util/round.dart';

class FfmpegUtil {

  static Future<Directory> _getTmpDir() async {
    var tmpDir = Directory(path.join((await getTemporaryDirectory()).path, RoundUtility.uuid()));
    if(!tmpDir.existsSync()){
    tmpDir.createSync();
    }
    return tmpDir;
  }

  static File _getFile(String path, {bool autoCreate=true}){
    var file= File(path);
    if(autoCreate && !file.existsSync()){
      file.createSync();
    }
    return file;
  }

  /// 加密：https://blog.csdn.net/hsj0170/article/details/107536827
  /// https://www.cnblogs.com/fieldtianye/p/13427303.html
  /// https://github.com/arthenica/ffmpeg-kit/issues/985
  static Future<M3u8Video?> toM3u8(M3u8Parameter param) async {
    var uuid = RoundUtility.uuid();
    Directory tmpDir = await _getTmpDir();
    File? keyInfoFile;
    File? keyFile;
    if(param.aesKey!=null){
      // 加密
      keyFile = _getFile(path.join(tmpDir.path,RoundUtility.roundFileName("key", prefix: "encrypt", len: 12)));
      var isk = keyFile.openWrite(mode: FileMode.writeOnly, encoding: utf8);
      isk.writeln(param.aesKey);
      isk.close();

      keyInfoFile = _getFile(path.join(tmpDir.path,RoundUtility.roundFileName("keyInfo")));
      var keyInfoFileIsk = keyInfoFile.openWrite(mode: FileMode.append, encoding: utf8);
      keyInfoFileIsk.writeln(keyFile.fileName);
      keyInfoFileIsk.writeln(keyFile.path);
      keyInfoFileIsk.close();
    }
    var outFilM3u8 = "${tmpDir.path}/ffmpeg_${DateTime.now().format("yyyyMMddHHmmss")}_index.m3u8";
    String cmd = "-i ${param.videoPath} -c:v copy -c:a copy -flags +cgop -g 15 -hls_time ${param.sliceTime} -hls_list_size 0 ${keyInfoFile!=null?'-hls_key_info_file ${keyInfoFile.path}':''} -hls_segment_filename ${tmpDir.path}/index_$uuid%5d.ts $outFilM3u8";
    Fimber.i("slice video cmd $cmd");
    var session = await FFmpegKit.execute(cmd);
    var returnCode = await session.getReturnCode();
    if(ReturnCode.isSuccess(returnCode)){
      if(keyFile!=null){
        // update key m3u8 file
        var key = keyFile.readAsStringSync().trim();
        if(param.encode!=null){
          var encodeKey =  param.encode?.call(key);
          keyFile.writeAsStringSync(encodeKey!, mode: FileMode.writeOnly, encoding: utf8);
          Fimber.i("encode key file $key => $encodeKey");
        }
      }
      return M3u8Video(File(outFilM3u8), tmpDir, keyFile: keyFile);
    }else{
      // error
      final log = await session.getOutput();
      Fimber.e("FFmpeg process failed with log: $log");
      return null;
    }
  }

}


class M3u8Video {

  /// m3u8文件
  final File m3u8File;

  /// ts 文件目录
  final Directory sliceDir;
  Uri get sliceDirUri=>Uri.file(sliceDir.path);

  /// 加密密钥，空表示没有加密
  final String? encryptedAesKey;

  /// 视频封面， 空表示没有视频封面
  final File? imgCover;

  /// 加密文件
  final File? keyFile;

  M3u8Video(this.m3u8File, this.sliceDir, {this.encryptedAesKey, this.imgCover, this.keyFile});
  
  /// m3u8文件文本内容
  String get m3u8Content => m3u8File.readAsStringSync();
  
  Future<List<File>> get tsFiles async {
    var hlsPlay = await HlsPlaylistParser.create().parseString(sliceDirUri, m3u8Content) as HlsMediaPlaylist;
    return hlsPlay.segments
        .map((e) => path.join(sliceDir.path, e.url!))
        .map((e) => File(e))
        .toList();
  }

  /// X-KEY
  Future<List<String>> get keys async {
    var hlsPlay = await HlsPlaylistParser.create().parseString(
        sliceDirUri, m3u8Content) as HlsMediaPlaylist;
    return hlsPlay.segments
        .map((e) => e.fullSegmentEncryptionKeyUri)
        .where((e) => e != null)
        .map((e) => e!)
        .toList();
  }

}

class M3u8Parameter {

  /// 是否生成视频封面
  final bool generateVideoCover;

  /// 视频判断长度 单位：秒
  final int sliceTime;

  /// 视频文件地址
  final String videoPath;

  /// 加密密钥
  final String? aesKey;

  /// 加密方法
  String? get encodeAesKey => aesKey==null?null:encode?.call(aesKey!);

  /// 加密方法
  String Function(String data)? encode;

  M3u8Parameter({
    required this.videoPath,
    this.sliceTime=10,
    this.generateVideoCover=false,
    this.aesKey,
    this.encode
  });

}
